﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Text.RegularExpressions;
using log4net;
using Truncation.Detector.Windows;

namespace Truncation.Detector.WinForm
{
    internal class ResourceReader
    {
        private static readonly ILog Reporter = LogManager.GetLogger("Detail.Reporter");
        private static readonly ILog Log = LogManager.GetLogger(typeof(ResourceReader));
        private readonly Dictionary<string, System.Windows.Forms.Control> _controls = new Dictionary<string, System.Windows.Forms.Control>();
        private readonly string _folder;
        private readonly string _resxName;
        private readonly string _baseFile;
        internal ResourceReader(string fileName)
        {
            //_dispatcher = dispatcher;
            if (String.IsNullOrEmpty(fileName))
            {
                Log.Error("the paramter of ResourceReader is NULL");
                throw new ArgumentNullException("fileName");
            }
            if (!File.Exists(fileName) || !fileName.ToLowerInvariant().EndsWith(".resx"))
            {
                Log.Error(String.Format("File: {0} doesn't exist", fileName));
                throw new FileNotFoundException(fileName);
            }
            _folder = Path.GetDirectoryName(fileName);
            _resxName = Path.GetFileNameWithoutExtension(fileName);
            _baseFile = fileName;
            Log.DebugFormat("Scanning {0}", fileName);
        }

        public void Run()
        {
            bool ret = ExtractFromInvariantResource(_baseFile);
            if (ret == false)
            {
                Log.Debug(String.Format("File: {0} has not localized resource", _baseFile));
                return;
            }
            var directoryInfo = new DirectoryInfo(_folder);
            string pattern = _resxName + ".*.resx";
            var files = directoryInfo.EnumerateFiles(pattern, SearchOption.TopDirectoryOnly);
            foreach (FileInfo file in files)
            {
                if (file.Name.ToLowerInvariant().Contains(_resxName.ToLowerInvariant() + ".resx"))
                {
                    continue;
                }
                string language = file.Name.Substring(_resxName.Length + 1).Replace(".resx", String.Empty);
                ExtractFromLocalizedResource(file.FullName, language);
            }
        }

        private bool ExtractFromInvariantResource(string fileName)
        {
            var resources = new Dictionary<string, ResXDataNode>();
            Read(fileName, ref resources);
            ResXDataNode localizableNode = null;
            resources.TryGetValue("$this.Localizable", out localizableNode);
            if (localizableNode == null)
            {
                return false;
            }
            var value = (bool)localizableNode.GetValue((ITypeResolutionService)null);
            if (value == false)
            {
                return false;
            }
            var items = resources.Where(p =>
                p.Key.EndsWith(".Type") &&
                p.Key.StartsWith(">>"));
            foreach (var item in items)
            {
                Match match = Regex.Match(item.Key, @">>(?<control>\w+)\.Type", RegexOptions.Singleline);
                string name = string.Empty;
                if (match.Success)
                {
                    name = match.Result("${control}");
                }
                string itemValue = item.Value.GetValue((ITypeResolutionService)null).ToString();
                System.Windows.Forms.Control control = null;
                if (itemValue.Contains("System.Windows.Forms.Button"))
                {
                    control = new System.Windows.Forms.Button { Name = name };
                }
                else if (itemValue.Contains("System.Windows.Forms.Label"))
                {
                    control = new System.Windows.Forms.Label { Name = name };
                }
                else if (itemValue.Contains("System.Windows.Forms.LinkLabel"))
                {
                    control = new System.Windows.Forms.LinkLabel { Name = name };
                }
                if (control != null)
                {
                    FillResource(control, name, resources);
                    Font font = control.Font;
                    string parentName = name;
                    while (font.Equals(SystemFonts.DefaultFont))
                    {
                        string key = String.Format(">>{0}.Parent", parentName);
                        if (resources.ContainsKey(key))
                        {
                            parentName = (string)resources[key].GetValue((ITypeResolutionService)null);
                            key = String.Format("{0}.Font", parentName);
                            if (resources.ContainsKey(key))
                            {
                                font = (Font)resources[key].GetValue((ITypeResolutionService)null);
                                break;
                            }
                        }
                        else
                        {
                            break;
                        }
                    }
                    if (!font.Equals(SystemFonts.DefaultFont))
                    {
                        control.Font = font;
                    }
                    _controls.Add(name, control);
                }

            }
            return true;
        }

        private void ExtractFromLocalizedResource(string fileName, string language)
        {
            Log.Info(String.Format("Start to parse {0} resource file: {1}", language, fileName));
            Summary.Instance.Counts ++;
            var resources = new Dictionary<string, ResXDataNode>();
            Read(fileName, ref resources);
            foreach (var item in _controls)
            {
                string textKey = String.Format("{0}.Text", item.Key);
                if (!resources.ContainsKey(textKey))
                {
                    // localized text doesn't exist
                    Log.Error(String.Format("{0} isn't localized in {1}", item.Key, fileName));
                    continue;
                }
                // clone control
                var control = (System.Windows.Forms.Control)Activator.CreateInstance(item.Value.GetType());
                foreach (PropertyInfo sourceProperty in item.Value.GetType().GetProperties())
                {
                    object newValue = sourceProperty.GetValue(item.Value, null);

                    MethodInfo mi = sourceProperty.GetSetMethod(true);
                    if (mi != null)
                    {
                        sourceProperty.SetValue(control, newValue, null);
                    }
                }
                FillResource(control, item.Key, resources);
                Summary.Instance.Items ++;
                string backup = control.Text;
                if (!MeasureControl(control, language))
                {
                    var serverity = Severity.Error;
                    if (!control.Text.Equals(backup))
                        serverity = Severity.Warning;
                    Report(serverity, fileName, textKey, backup, resources[textKey].GetNodePosition().Y, resources[textKey].GetNodePosition().X);

                }
            }
            Log.Info(String.Format("End to parse {0} resource file: {1}", language, fileName));
        }

        private void Read(string fileName, ref Dictionary<string, ResXDataNode> resources)
        {
            var reader = new ResXResourceReader(fileName)
            {
                BasePath = _folder,
                UseResXDataNodes = true
            };
            IDictionaryEnumerator iterator = reader.GetEnumerator();
            while (iterator.MoveNext())
            {
                if (!resources.ContainsKey(iterator.Key.ToString()))
                {
                    resources.Add(iterator.Key.ToString(), (ResXDataNode)iterator.Value);
                }
            }
            iterator = reader.GetMetadataEnumerator();
            while (iterator.MoveNext())
            {
                if (!resources.ContainsKey(iterator.Key.ToString()))
                {
                    resources.Add(iterator.Key.ToString(), (ResXDataNode)iterator.Value);
                }
            }
        }

        private void FillResource(object control, string name, Dictionary<string, ResXDataNode> resource)
        {
            PropertyInfo prop = null;
            BindingFlags flags = BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance;
            var styles = resource.Where(p => p.Key.StartsWith(name + "."));
            foreach (var style in styles)
            {
                string propName = style.Key.Substring(name.Length + 1);
                prop = control.GetType().GetProperty(propName, flags);
                if (prop != null)
                    prop.SetValue(control, style.Value.GetValue((ITypeResolutionService)null), null);
                else
                {
                    Log.DebugFormat("Cannot get property: {0}", propName);
                }
            }
        }

        private bool MeasureControl(System.Windows.Forms.Control control, string language)
        {
            bool ret = true;
            Log.Info(String.Format("Start to measure the control({1}), its font is {0}", control.Font.ToString(), control.Name));
            // if the text of control is different from that of invariant control, then consider it translated
            string name = control.Name;
            string text = control.Text;
            if (control.AutoSize != true)
            {
                if (_controls[name].Text.Equals(text))
                {
                    Log.Info(String.Format("{0} will be translated to {1}", text, language));
                    text = Truncation.Detector.Windows.Translation.Translate(text, language);
                }
                control.Text = text;
                if (control is System.Windows.Forms.Button)
                {
                    var button = new Controls.Button(control);
                    ret = button.Measure();
                }
                else if (control is System.Windows.Forms.Label)
                {
                    var label = new Controls.Label(control);
                    ret = label.Measure();
                }
                else if (control is System.Windows.Forms.LinkLabel)
                {
                    var linkLabel = new Controls.LinkLabel(control);
                    ret = linkLabel.Measure();
                }
                else
                {

                }
            }
            Log.Info(String.Format("End to measure the control"));
            return ret;
        }

        private void Report(Severity severity, string fileName, string key, string value, int line, int column)
        {
            Uri file = new Uri(fileName);
            Uri folder = new Uri(Environment.CurrentDirectory + Path.DirectorySeparatorChar);
            const int errCode = 5004;
            if (severity == Severity.Error)
            {
                Summary.Instance.Errors ++;
            }
            else if (severity == Severity.Warning)
            {
                Summary.Instance.Warnings ++;
            }
            const string nodeTemplate = @"  <data name=""{0}"" xml:space=""preserve"">
    <value>{1}</value>
  </data>";
            string content = String.Format(nodeTemplate, key, value);
            int length = content.Split(new[] { Environment.NewLine }, StringSplitOptions.None)[0].Length;
            string msg = String.Format("{0},{1},{2},{3},{4},{5},\"{6}\"", folder.MakeRelativeUri(file).ToString(), line, column, length, severity, errCode, content.Replace("\"", "\"\""));
            Reporter.Info(msg);
        }
    }

    public enum Severity
    {
        Error,
        Warning
    }
}